#include "GlDxSpotLight.h"

#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
#include "GlCommon.h"
#include "GlDxSprite.h"
#include "GlMatrix4x4.h"
#include "GlSection.h"
#include "GlSpriteRenderer.h"
#include "Utility.h"
#include "UtilityAssert.h"

namespace GraphicLibrary
{
	//------------------------------------------------------
	SpotLight* DxSpotLight::Factory::create(const float angle, const float distance)
	{
		return new DxSpotLight(mDevice, angle, distance);
	}
}

namespace GraphicLibrary
{
	//------------------------------------------------------
	DxSpotLight::DxSpotLight(ID3D11Device *device, const float angle, const float distance)
		: SpotLight(angle, distance)
		, mVertexBuffer(0)
		, mIndexBuffer(0)
		, mDevice(device)
	{
		setSelf(this);
		createBuffers(mDevice);
	}

	//------------------------------------------------------
	DxSpotLight::~DxSpotLight()
	{
		if(mVertexBuffer)
			mVertexBuffer->Release();
		if(mIndexBuffer)
			mIndexBuffer->Release();
	}

	//------------------------------------------------------
	Matrix4x4 DxSpotLight::getTransformationMatrix() const
	{
		Matrix4x4 mat;
		mat.setRotationZ(getRotateAngle());
		Matrix4x4 tmp;
		tmp.setTranslocation(getX(), getY(), 0.0f);
		mat *= tmp;
		return mat;
	}

	//------------------------------------------------------
	void DxSpotLight::apply(ID3D11DeviceContext *ic) const
	{
		if(mVertexBuffer)
			mVertexBuffer->Release();
		if(mIndexBuffer)
			mIndexBuffer->Release();
		generateLightMesh();
		createBuffers(mDevice);

		UINT strides = sizeof(VertexData);
		UINT offsets = 0;
		ic->IASetVertexBuffers(0, 1, &mVertexBuffer, &strides, &offsets);
		ic->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
	}

	//------------------------------------------------------
	void DxSpotLight::generateLightMesh() const
	{
		Triangle base;
		float l = mDistance / cos(mAngle * 0.5f);
		base.mVertexes[0].mX = 0.0f;
		base.mVertexes[0].mY = 0.0f;

		base.mVertexes[1].mX = -sin(mAngle * 0.5f) * l;
		base.mVertexes[1].mY = cos(mAngle * 0.5f) * l;

		base.mVertexes[2].mX = -base.mVertexes[1].mX;
		base.mVertexes[2].mY = base.mVertexes[1].mY;

		DxSpriteArray spritesInRange;
		fillSpritesInRange(spritesInRange);

		//Triangle::TriangleArray first, second, *source, *result;
		//source = &first;
		//result = &second;
		//source->push_back(base);
		//Matrix4x4 invTransMat = getInvTransformationMatrix();
		//for(DxSpriteArray::const_iterator i = spritesInRange.begin(); i != spritesInRange.end(); ++i)
		//{
		//	result->clear();
		//	removeSpriteShadow(*source, *i, *result, invTransMat);
		//	Utility::swap(source, result);
		//}

		//mLighMesh = *source;
		mLighMesh.clear();
		SectionDataArray sections;
		sections.clear();
		fillSectionArrayWithSpriteBoundingBox(base, spritesInRange, sections);
		//for(SectionDataArray::iterator i = sections.begin(); i != sections.end(); ++i)
		//{
		//	std::cout << (*i)->mPosition << ":" << std::endl;
		//	std::cout << "begin:" << std::endl;
		//	for(SectionArray::const_iterator j = (*i)->mSectionBegins.begin(); j != (*i)->mSectionBegins.end(); ++j)
		//		std::cout << "\t" << "Section(" << j->mBegin << ", " << j->mEnd << std::endl;
		//	std::cout << "end:" << std::endl;
		//	for(SectionArray::const_iterator j = (*i)->mSectionEnds.begin(); j != (*i)->mSectionEnds.end(); ++j)
		//		std::cout << "\t" << "Section(" << j->mBegin << ", " << j->mEnd << std::endl;
		//}
		fillLighMesh(base, sections, mLighMesh);
	}

	//------------------------------------------------------
	void DxSpotLight::createBuffers(ID3D11Device *device) const
	{
		VertexData *vData = new VertexData[mLighMesh.size() * 3];
		IndexData *iData = new IndexData[mLighMesh.size() * 3];

		for(Triangle::TriangleArray::size_type i = 0; i != mLighMesh.size(); ++i)
		{
			vData[3 * i].mPos[0] = mLighMesh[i].mVertexes[0].mX;
			vData[3 * i].mPos[1] = mLighMesh[i].mVertexes[0].mY;
			vData[3 * i].mUV[0] = getU(mLighMesh[i].mVertexes[0]);
			vData[3 * i].mUV[1] = getV(mLighMesh[i].mVertexes[0]);
			iData[3 * i] = 3 * i;

			vData[3 * i + 1].mPos[0] = mLighMesh[i].mVertexes[1].mX;
			vData[3 * i + 1].mPos[1] = mLighMesh[i].mVertexes[1].mY;
			vData[3 * i + 1].mUV[0] = getU(mLighMesh[i].mVertexes[1]);
			vData[3 * i + 1].mUV[1] = getV(mLighMesh[i].mVertexes[1]);
			iData[3 * i + 1] = 3 * i + 1;

			vData[3 * i + 2].mPos[0] = mLighMesh[i].mVertexes[2].mX;
			vData[3 * i + 2].mPos[1] = mLighMesh[i].mVertexes[2].mY;
			vData[3 * i + 2].mUV[0] = getU(mLighMesh[i].mVertexes[2]);
			vData[3 * i + 2].mUV[1] = getV(mLighMesh[i].mVertexes[2]);
			iData[3 * i + 2] = 3 * i + 2;
		}

		if(mLighMesh.empty())
			mVertexBuffer = mIndexBuffer = 0;
		else
		{
			mVertexBuffer = DxGraphicComponent::createVertexBuffer(device, mLighMesh.size() * 3 * sizeof(VertexData), vData);
			mIndexBuffer = DxGraphicComponent::createIndexBuffer(device, mLighMesh.size() * 3 * sizeof(IndexData), iData);
		} 

		delete [] vData;
		delete [] iData;
	}

	//------------------------------------------------------
	void DxSpotLight::fillSpritesInRange(DxSpriteArray &sprites) const
	{
		sprites.clear();
		for(SpriteRenderer::SpriteIterator i; i.hasMore(); i.next())
		{
			const DxSprite *sprite = i.peek();
			if(sprite->canCastShadow() && sprite->getBoundingCircleRadius() + mDistance > Position::Distance(getPosition(), sprite->getPosition()))
				sprites.push_back(sprite);
		}
	}

	//------------------------------------------------------
	void DxSpotLight::removeSpriteShadow(const Triangle::TriangleArray &source, const DxSprite *sprite, Triangle::TriangleArray &result, const Matrix4x4 &invTransMat) const
	{
		for(Triangle::TriangleArray::const_iterator i = source.begin(); i != source.end(); ++i)
			removeSpriteShadow(*i, sprite, result, invTransMat);
	}

	//------------------------------------------------------
	void DxSpotLight::removeSpriteShadow(const Triangle &source, const DxSprite *sprite, Triangle::TriangleArray &result, const Matrix4x4 &invTransMat) const
	{
		if(sprite->contains(getPosition()))
			return;

		Matrix4x4 mat = sprite->getTransformationMatrix();
		mat *= invTransMat;
		Position vertexes[4];
		sprite->fillVertexes(vertexes);
		for(int i = 0; i != 4; ++i)
			vertexes[i] = vertexes[i] * mat;

		Triangle::TriangleArray first, second, *s, *r;
		s = &first;
		r = &second;
		s->push_back(source);

		removeSectionShadow(*s, createExtendedSection(vertexes[0], vertexes[1]), *r);
		Utility::swap(s, r);
		r->clear();

		removeSectionShadow(*s, createExtendedSection(vertexes[1], vertexes[2]), *r);
		Utility::swap(s, r);
		r->clear();

		removeSectionShadow(*s, createExtendedSection(vertexes[2], vertexes[3]), *r);
		Utility::swap(s, r);
		r->clear();

		removeSectionShadow(*s, createExtendedSection(vertexes[3], vertexes[0]), *r);
		Utility::swap(s, r);
		result.resize(result.size() + s->size());
		std::copy_backward(s->begin(), s->end(), result.end());
	}

	//------------------------------------------------------
	void DxSpotLight::removeSectionShadow(const Triangle::TriangleArray &source, const Section &section, Triangle::TriangleArray &result) const
	{
		for(Triangle::TriangleArray::const_iterator i = source.begin(); i != source.end(); ++i)
			removeSectionShadow(*i, section, result);
	}

	//------------------------------------------------------
	void DxSpotLight::removeSectionShadow(const Triangle &triangle, const Section &section, Triangle::TriangleArray &result) const
	{
		Section sections[3];
		sections[0] = Section(triangle.mVertexes[0], triangle.mVertexes[1]);
		sections[1] = Section(triangle.mVertexes[1], triangle.mVertexes[2]);
		sections[2] = Section(triangle.mVertexes[2], triangle.mVertexes[0]);

		bool hasIntersection[3];
		Position intersections[3];
		int intersectionCount = 0;
		for(int i = 0; i != 3; ++i)
			if((hasIntersection[i] = Section::GetIntersection(sections[i], section, intersections + i)))
				++intersectionCount;
		switch(intersectionCount)
		{
			case 0 :
				removeSectionShadow_withoutIntersection(triangle, section, result);
				break;
			case 1 :
				removeSectionShadow_oneIntersection(triangle, section, result, intersections, hasIntersection);
				break;
			case 2 :
				removeSectionShadow_twoIntersection(triangle, section, result, intersections, hasIntersection);
				break;
			case 3 :
				if(intersections[0] == intersections[1])
					hasIntersection[1] = false;
				if(intersections[1] == intersections[2])
					hasIntersection[1] = false;
				removeSectionShadow_twoIntersection(triangle, section, result, intersections, hasIntersection);
				break;
		}
	}

	//------------------------------------------------------
	void DxSpotLight::removeSectionShadow_withoutIntersection(const Triangle &triangle, const Section &section, Triangle::TriangleArray &result, const bool force) const
	{
		if(!triangle.contains(section.mBegin) && !force)
		{
			result.push_back(triangle);
			return;
		}

		Position positions[6];
		positions[0] = triangle.mVertexes[1];
		positions[5] = triangle.mVertexes[2];
		positions[2] = section.mBegin;
		positions[3] = section.mEnd;
		Section surface = createExtendedSection(triangle.mVertexes[1], triangle.mVertexes[2]);
		positions[1] = getProjection(positions[2], triangle.mVertexes[0], surface);
		positions[4] = getProjection(positions[3], triangle.mVertexes[0], surface);
		if(positions[1].mX > positions[4].mX)
		{
			Utility::swap(positions[2], positions[3]);
			Utility::swap(positions[1], positions[4]);
		}
		else if(positions[1].mX == positions[4].mX)
		{
			result.push_back(triangle);
			return;
		}
		insertToTriangleArray(result,
			Triangle(
				triangle.mVertexes[0],
				positions[0],
				positions[1]
			)
		);
		insertToTriangleArray(result,
			Triangle(
				triangle.mVertexes[0],
				positions[2],
				positions[3]
			)
		);
		insertToTriangleArray(result,
			Triangle(
				triangle.mVertexes[0],
				positions[4],
				positions[5]
			)
		);
	}

	//------------------------------------------------------
	void DxSpotLight::removeSectionShadow_oneIntersection(const Triangle &triangle, const Section &section, Triangle::TriangleArray &result, const Position *iPositions, const bool *intersects) const
	{
		int intersectionIndex = 0;
		while(!intersects[intersectionIndex])
			++intersectionIndex;

		if((iPositions[intersectionIndex] == section.mBegin && !triangle.contains(section.mEnd))
		|| (iPositions[intersectionIndex] == section.mEnd && !triangle.contains(section.mBegin)))
		{
			result.push_back(triangle);
			return;
		}

		Section surface = createExtendedSection(triangle.mVertexes[1], triangle.mVertexes[2]);
		const Position *insidePoint = triangle.contains(section.mBegin)
									? &section.mBegin
									: &section.mEnd;
		Position projection = getProjection(*insidePoint, triangle.mVertexes[0], surface);
		switch(intersectionIndex)
		{
			case 1 :
				{
					if(triangle.contains(section.mBegin))
						removeSectionShadow_withoutIntersection(triangle, createExtendedSection(iPositions[intersectionIndex], section.mBegin), result, true);
					else
						removeSectionShadow_withoutIntersection(triangle, createExtendedSection(iPositions[intersectionIndex], section.mEnd), result, true);
					break;
				}
			case 0 :
				{
					insertToTriangleArray(result, Triangle(triangle.mVertexes[0], iPositions[intersectionIndex], *insidePoint));
					insertToTriangleArray(result, Triangle(triangle.mVertexes[0], projection, triangle.mVertexes[2]));
					break;
				}
			case 2 :
				{
					insertToTriangleArray(result, Triangle(triangle.mVertexes[0], triangle.mVertexes[1], projection));
					insertToTriangleArray(result, Triangle(triangle.mVertexes[0], *insidePoint, iPositions[intersectionIndex]));
					break;
				}
		}
	}

	//------------------------------------------------------
	void DxSpotLight::removeSectionShadow_twoIntersection(const Triangle &triangle, const Section &section, Triangle::TriangleArray &result, const Position *iPositions, const bool *intersects) const
	{
		if(intersects[0] && intersects[1])
		{
			insertToTriangleArray(result, Triangle(triangle.mVertexes[0], iPositions[0], iPositions[1]));
			insertToTriangleArray(result, Triangle(triangle.mVertexes[0], iPositions[1], triangle.mVertexes[2]));
		}
		else if(intersects[0] && intersects[2])
			insertToTriangleArray(result, Triangle(triangle.mVertexes[0], iPositions[0], iPositions[2]));
		else if(intersects[1] && intersects[2])
		{
			insertToTriangleArray(result, Triangle(triangle.mVertexes[0], triangle.mVertexes[1], iPositions[1]));
			insertToTriangleArray(result, Triangle(triangle.mVertexes[0], iPositions[1], iPositions[2]));
		}
	}

	//------------------------------------------------------
	void DxSpotLight::insertToTriangleArray(Triangle::TriangleArray &result, const Triangle &triangle) const
	{
		if(!triangle.isRealTriangle())
			return;
		result.push_back(triangle);
	}

	//------------------------------------------------------
	Section DxSpotLight::createExtendedSection(const Position &begin , const Position &end) const
	{
		Section s(begin, end);
		//s.scale(1.0f + 0.01f / s.length());
		return s;
	}

	//------------------------------------------------------
	Position DxSpotLight::getProjection(const Position &point, const Position &origin, const Section &surface) const
	{
		Section ray(origin, point);
		Position projection;
		Section::GetIntersection(ray, surface, &projection);
		return projection;
	}

	//------------------------------------------------------
	Matrix4x4 DxSpotLight::getInvTransformationMatrix() const
	{
		Matrix4x4 mat;
		mat.setTranslocation(-getX(), -getY(), 0.0f);
		Matrix4x4 tmp;
		tmp.setRotationZ(-getRotateAngle());
		mat *= tmp;
		return mat;
	}

	//------------------------------------------------------
	void DxSpotLight::fillSectionArrayWithSpriteBoundingBox(const Triangle &triangle, const DxSpriteArray &sprites, SectionDataArray &sections) const
	{
		Position origin = getPosition();
		Matrix4x4 invTransMat = getInvTransformationMatrix();
		for(DxSpriteArray::const_iterator i = sprites.begin(); i != sprites.end(); ++i)
		{
			const DxSprite *s = *i;
			if(s->contains(origin))
				continue;

			Matrix4x4 mat = s->getTransformationMatrix();
			mat *= invTransMat;
			Position vertexes[4];
			s->fillVertexes(vertexes);
			for(int i = 0; i != 4; ++i)
				vertexes[i] = vertexes[i] * mat;

			add(triangle, sections, Section(vertexes[0], vertexes[1]));
			add(triangle, sections, Section(vertexes[1], vertexes[2]));
			add(triangle, sections, Section(vertexes[2], vertexes[3]));
			add(triangle, sections, Section(vertexes[3], vertexes[0]));
		}
		add(triangle, sections, Section(triangle.mVertexes[1], triangle.mVertexes[2]));
	}

	//------------------------------------------------------
	void DxSpotLight::fillLighMesh(const Triangle &triangle, const SectionDataArray &sections, Triangle::TriangleArray &lightMesh) const
	{
		//mLighMesh.push_back(triangle);
		SectionArray activeSections;
		SectionArray lowest;
		for(SectionDataArray::const_iterator i = sections.begin(); i != sections.end(); ++i)
		{
			const SectionData *section = *i;

			if(!lowest.empty())
			{
				SectionArray l;
				findLowests(triangle, lowest, section->mPosition, l);
				Section *selected = &(l.front());
				SectionDataArray::const_iterator prev = i - 1;
				Triangle t(triangle.mVertexes[0]
						 , getPositionAt(triangle, *selected, (*prev)->mPosition)
						 , getPositionAt(triangle, *selected, section->mPosition));
				insertToTriangleArray(lightMesh, t);
			}

			for(SectionArray::const_iterator j = section->mSectionBegins.begin(); j != section->mSectionBegins.end(); ++j)
				activeSections.add(*j);
			for(SectionArray::const_iterator j = section->mSectionEnds.begin(); j != section->mSectionEnds.end(); ++j)
				activeSections.remove(*j);

			findLowests(triangle, activeSections, section->mPosition, lowest);
		}
	}

	//------------------------------------------------------
	DxSpotLight::SectionDataArray::iterator DxSpotLight::findPlace(SectionDataArray &sections, const Position &pos) const
	{
		class Less
		{
			public:
				inline bool operator () (const SectionData *left, const Position &right) const		{return left->mPosition.mX < right.mX;}
				inline bool operator () (const Position &left, const SectionData *right) const		{return left.mX < right->mPosition.mX;}
				inline bool operator () (const SectionData *left, const SectionData *right) const	{return left->mPosition.mX < right->mPosition.mX;}
		};
		return std::lower_bound(sections.begin(), sections.end(), pos, Less());
	}

	//------------------------------------------------------
	void DxSpotLight::add(const Triangle &triangle, SectionDataArray &sections, Section section) const
	{
		Position beginProjection;
		Position endProjection;
		if(!setSectionToTriangle(triangle, section, beginProjection, endProjection))
			return;

		SectionDataArray::iterator i = findPlace(sections, beginProjection);
		SectionData *data = 0;
		if(i == sections.end() || Utility::abs(((*i)->mPosition - beginProjection).length()) > 0.0001)
			sections.insert(i, data = new SectionData(beginProjection));
		else
			data = *i;
		data->mSectionBegins.add(section);

		i = findPlace(sections, endProjection);
		data = 0;
		if(i == sections.end() || Utility::abs(((*i)->mPosition - endProjection).length()) > 0.0001)
			sections.insert(i, data = new SectionData(endProjection));
		else
			data = *i;
		data->mSectionEnds.add(section);
	}

	//------------------------------------------------------
	bool DxSpotLight::setSectionToTriangle(const Triangle &triangle, Section &section, Position &beginProjection, Position &endProjection) const
	{
		Section triangleBounds[3] = {
			Section(triangle.mVertexes[0], triangle.mVertexes[1]),
			Section(triangle.mVertexes[1], triangle.mVertexes[2]),
			Section(triangle.mVertexes[2], triangle.mVertexes[0])
		};
		bool containsBegin = triangle.contains(section.mBegin);
		bool containsEnd = triangle.contains(section.mEnd);
		bool beginProjIsSet = false;
		bool endProjIsSet = false;
		if(!containsBegin || !containsEnd)
		{
			bool intersects[3];
			Position intersections[3];
			for(int i = 0; i != 3; ++i)
				intersects[i] = Section::GetIntersection(triangleBounds[i], section, intersections + i);

			int i = 0;
			while(!intersects[i] && i != 3)
				++i;
			if(i == 3)
				return false;

			bool iIsInUse = false;
			if(!containsBegin)
			{
				iIsInUse = true;
				section.mBegin = intersections[i];
				beginProjIsSet = true;
				switch(i)
				{
					case 0 :
						beginProjection = triangle.mVertexes[1];
						break;
					case 2 :
						beginProjection = triangle.mVertexes[2];
						break;
					case 1 :
						beginProjection = section.mBegin;
						break;
				}
			}

			if(!containsEnd)
			{
				int j = i;
				if(iIsInUse)
					++j;
				bool repeatSelection = false;
				do
				{
					while(!intersects[j] && j != 3)
						++j;
					if(j == 3)
						return false;
					repeatSelection = iIsInUse && intersections[i] == intersections[j];
					if(repeatSelection)
						++j;
				}
				while(repeatSelection);
				section.mEnd = intersections[j];
				endProjIsSet = true;
				switch(j)
				{
					case 0 :
						endProjection = triangle.mVertexes[1];
						break;
					case 2 :
						endProjection = triangle.mVertexes[2];
						break;
					case 1 :
						endProjection = section.mEnd;
						break;
				}
			}

			containsBegin = true;
			containsEnd = true;
		}

		if(!beginProjIsSet)
		{
			beginProjIsSet = true;
			beginProjection = getProjection(section.mBegin, triangle.mVertexes[0], triangleBounds[1]);
		}

		if(!endProjIsSet)
		{
			endProjIsSet = true;
			endProjection = getProjection(section.mEnd, triangle.mVertexes[0], triangleBounds[1]);
		}
		if(endProjection.mX < beginProjection.mX)
		{
			Utility::swap(endProjection, beginProjection);
			Utility::swap(section.mBegin, section.mEnd);
		}
		return true;
	}

	//------------------------------------------------------
	void DxSpotLight::findLowests(const Triangle &triangle, const SectionArray &sections, const Position &projection, SectionArray &result) const
	{
		// ToDo: Potential loss of precision!!!
		result.clear();
		float lowestHeight = 0.0f;
		if(sections.empty())
			return;
		lowestHeight = getHeightAt(triangle, sections.front(), projection);
		result.add(sections.front());
		for(SectionArray::const_iterator j = sections.begin() + 1; j != sections.end(); ++j)
		{
			float height = getHeightAt(triangle, *j, projection);
			if(Utility::abs(height - lowestHeight) < 0.001)
				result.add(*j);
			else if(height < lowestHeight)
			{
				lowestHeight = height;
				result.clear();
				result.add(*j);
			}
		}
	}

	//------------------------------------------------------
	float DxSpotLight::getHeightAt(const Triangle &triangle, const Section &section, const Position &projection) const
	{
		return (getPositionAt(triangle, section, projection) - triangle.mVertexes[0]).length();
	}

	//------------------------------------------------------
	Position DxSpotLight::getPositionAt(const Triangle &triangle, const Section &section, const Position &projection) const
	{
		Section p(triangle.mVertexes[0], projection);
		Position intersection;
		Section::GetIntersection(p, section, &intersection);
		return intersection;
	}
}
